Datomic 就像許多的資料庫一樣,資料量大了之後,就算是一般的查詢也很容易變慢。要改進 Datomic 的效能時,可以應用的方法有兩種:
指導原則是基於 Datomic 的設計原理、實務開發經驗而得出的。
一個子句的未綁定的變數愈多,它就愈可能去比對到愈多的資料原子 (datom),也因此,如果我們將那些未綁定變數較少的子句置前,一個查詢總共需要比對的資料原子就有可能會最少。
Datomic 的 where 子句是依次執行的,所以如果我們將篩選最嚴格的子句置前的話,一個查詢總共需要比對的資料原子就有可能會最少。
以下方來講,如果 [?e :only/matches ?few]
會比對成功的資料原子比 [?e :will/match ?many]
來得少的話,這個順序就有較高的機會有較好的效能。
:where [?e :only/matches ?few]
[?e :will/match ?many]
Datomic 由於內部實作了事件溯源,提供了值導向 (value-oriented programming) 的語意,所以可以保証不會發生資料的不一致。因為對於 Datomic 來說,整個資料庫在任何時間點都是對應不同的值,而且只要知道時間點,就可以存取對應的資料庫值,就像 git 一樣。
也正因 Datomic 提供了這種方便性,很多使用 Datomic 的工程師就真的卯起來寫 N+1 的查詢。反正不會有資料的不一致的問題嘛!是的,不會有,可是會有效能的問題。
也因此,對於已知某些查詢會比對大量的資料原子,特別要注意是否使用了 N+1 的寫法。如果是的話,修改查詢以讓查詢的次數可以減少,就可以減少大量的 I/O 時間。
Datomic 提供兩個量測工具,一個用指導邏輯層面的最佳化:查詢統計 (Query Stats);另一個則跟布署與架構上的最佳化很有關:I/O 統計 (I/O Stats)。
範例:
(d/query {:query '[:find (count ?a)
:in $ ?aname
:where [?a :artist/name ?aname]]
:args [db "The Beatles"]
:query-stats true})
查詢統計可以提供:每個子句確切比對到的資料原子數。我們感覺上猜測的「篩選最嚴格的子句」未必是最嚴格的,搭配查詢統計可以讓我們得到精確的資料。
範例:
(d/query {:query '[:find (count ?a)
:in $ ?aname
:where [?a :artist/name ?aname]]
:args [db "The Beatles"]
:io-context :artist/by-name})
Datomic 在架構設計上,使用了多層的快取 (cache),也因此我們從觀察這些快取的 I/O 資訊,就可以大概掌握該如何調整快取的布署。
Datomic 的快取由近到遠依序是:
快取的名稱 | 快取儲存的對象 |
---|---|
:ocache | 通過記憶體對物件快取 (Java物件的快取) 訪問的區段 (segment) 數量 |
:valcache | 通過 Valcache (SSD 快取) 訪問的區段數量 |
:memcached | 通過 Memcached 訪問的區段數量 |
(storage of record) :ddb/:sql/:cass etc. | 通過 Datomic 存儲訪問的區段數量(:ddb/:sql/:cass 是目前正在使用的存儲協議) |
:inflight-lookup-ms | 等待相同區段的平行處理請求所花費的總時間 |